namespace ScrumSprintMonitor.Modules.WorkItemMonitor.ViewModels
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;

	using Infrastructure;

	using Microsoft.Practices.ServiceLocation;

	using UI.Wpf.ViewModels;

	#endregion

	public abstract class SprintViewModelBase : ViewModelBase
	{
		private static readonly IList<IWorkItemAdapter> EmptyWorkItemsList = new List<IWorkItemAdapter>().AsReadOnly();

		static SprintViewModelBase()
		{
			var dependsOn = Register<SprintViewModelBase>();

			dependsOn.Call(me => me.Exception = (me.CurrentSprint != null ? me.CurrentSprint.Exception : null)).OnChanged(
				me => me.ReportableData.CurrentSprint.Exception);
			dependsOn.Call(me => me.OnPropertyChanged("CurrentSprint")).OnChanged(me => me.ReportableData.CurrentSprint);
			dependsOn.Call(me => me.OnSprintDataInitialized()).OnChanged(me => me.CurrentSprint);
			dependsOn.Call(me =>
			               {
			               	me.AdviseCurrentSprintWorkItemStoreEvents();
			               	me.CalculateWorkItemData();
			               }).OnChanged(me => me.ReportableData.CurrentSprint.WorkItemStore);
		}

		protected SprintViewModelBase(IServiceLocator serviceLocator, bool adviseWorkItemStore)
		{
			this.serviceLocator = serviceLocator;
			this.adviseWorkItemStore = adviseWorkItemStore;
		}

		protected virtual void OnSprintDataInitialized()
		{
		}

		protected void CalculateWorkItemData()
		{
			IList<IWorkItemAdapter> workItems = null;

			if (CurrentSprint != null && CurrentSprint.WorkItemStore != null)
			{
				workItems = CurrentSprint.WorkItemStore.WorkItems;
			}

			if (workItems == null)
			{
				workItems = EmptyWorkItemsList;
			}

			OnCalculateWorkItemData(workItems);
		}

		protected virtual void OnCalculateWorkItemData(IList<IWorkItemAdapter> workItems)
		{
		}

		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);

			if (disposing)
			{
				ReportableData = null;
			}
		}

		#region Event Handlers

		private void AdviseCurrentSprintWorkItemStoreEvents()
		{
			if (this.workItemStoreEventSource != null)
			{
				this.workItemStoreEventSource.WorkItemsUpdated -= OnWorkItemsUpdated;
				this.workItemStoreEventSource = null;
			}

			if (this.adviseWorkItemStore)
			{
				if (CurrentSprint != null)
				{
					this.workItemStoreEventSource = CurrentSprint.WorkItemStore;
					if (this.workItemStoreEventSource != null)
					{
						this.workItemStoreEventSource.WorkItemsUpdated += OnWorkItemsUpdated;
					}
				}
			}
		}

		private void OnWorkItemsUpdated(object sender, EventArgs e)
		{
			CalculateWorkItemData();
		}

		#endregion

		#region Private Fields

		private readonly bool adviseWorkItemStore;
		private readonly IServiceLocator serviceLocator;
		private Exception exception;
		private IReportableData reportableData;
		private IWorkItemStore workItemStoreEventSource;

		#endregion

		#region Private Methods

		protected IReportableData ReportableData
		{
			[DebuggerStepThrough]
			get { return this.reportableData; }
			private set
			{
				if (value != this.reportableData)
				{
					this.reportableData = value;

					OnPropertyChanged("ReportableData");
				}
			}
		}

		protected ISprint CurrentSprint
		{
			[DebuggerStepThrough]
			get
			{
				EnsureConnected();
				return (ReportableData != null ? ReportableData.CurrentSprint : null);
			}
		}

		protected Exception Exception
		{
			get
			{
				GuardDisposed();

				if (this.exception == null)
				{
					EnsureConnected(false);
				}

				return this.exception;
			}
			set
			{
				if (value != this.exception)
				{
					this.exception = value;

					OnPropertyChanged("Exception");
				}
			}
		}

		protected IServiceLocator ServiceLocator
		{
			[DebuggerStepThrough]
			get { return this.serviceLocator; }
		}

		protected void EnsureConnected()
		{
			EnsureConnected(false);
		}

		protected void EnsureConnected(bool throwException)
		{
			GuardDisposed();

			if (ReportableData == null && this.serviceLocator != null)
			{
				try
				{
					ReportableData = this.serviceLocator.GetInstance<IReportableData>();
				}
				catch (Exception e)
				{
					bool unitTesting = (SyncContext == null);

					if (unitTesting)
					{
						throwException = true;
					}

					Exception = e;

					if (throwException)
					{
						throw;
					}
				}
			}
		}

		#endregion
	}
}